package ru.compft.common.services.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import ru.compft.common.dao.RoleDao;
import ru.compft.common.data.AppRole;
import ru.compft.common.services.RolesService;

import javax.annotation.Resource;
import java.util.*;

/**
 * User: VVasiliev
 * Date: 14.06.12
 * Time: 14:03
 * Реализация сервиса работы с ролями пользователя
 */
@Service
public class RolesServiceImpl implements RolesService {
    private static final Logger logger = LoggerFactory.getLogger(RolesServiceImpl.class);

    @Resource(name = "roleDao")
    private RoleDao roleDao;

    /**
     * Получение списка всех ролей системы
     * На данный метод имеют право только пользователи с определенными ролями
     * Кешируем возвращенный результат
     *
     * @return
     */
    @Override
    public List<AppRole> getAllRoles() {
        return roleDao.getAllRoles();
    }

    /**
     * Получение ролей от определенного индекса (from) и определенного количества (count)
     *
     * @param from
     * @param count
     * @return
     */
    @Override
    public List<AppRole> getRoles(int from, int count) {
        return roleDao.getRoles(from, count);
    }

    /**
     * Подсчитываем все роли в системе
     *
     * @return
     */
    @Override
    public int countAllRoles() {
        return roleDao.countAllRoles();
    }

    /**
     * Получаем роль по ID
     * Кешируем возвращенный результат по ID
     *
     * @param roleId
     * @return
     */
    @Override
    public AppRole findRoleById(Long roleId) {
        return roleDao.getRoleById(roleId);
    }

    /**
     * Поиск роли по коду
     * Кешируем возвращенный результат по code
     *
     * @param code
     * @return
     */
    @Override
    public AppRole findRoleByCode(String code) {
        return roleDao.getRoleByCode(code);
    }

    /**
     * Создание роли
     * Очистим весь кеш для ролей.
     * Полученный результат поместим в кеш
     * Кеш по всем ролям снова перечитается лишь при getAllRoles()
     *
     * @param role
     * @return
     */
    @Override
    @Transactional
    public AppRole addRole(AppRole role) {
        if (role != null) {
            return roleDao.persist(role);
        }
        return null;
    }

    /**
     * Изменение свойств роли
     * Очистим весь кеш для ролей.
     * Полученный результат поместим в кеш
     * Кеш по всем ролям снова перечитается лишь при getAllRoles()
     *
     * @param role
     * @return
     */
    @Override
    @Transactional
    public AppRole updateRole(AppRole role) {
        return roleDao.update(role);
    }

    /**
     * Удаление роли
     * Очистим весь кеш для ролей.
     * Кеш по всем ролям снова перечитается лишь при getAllRoles()
     *
     * @param role
     * @return
     */
    @Override
    @Transactional
    public Boolean deleteRole(AppRole role) {
        return roleDao.delete(role);
    }

    /**
     * Специальный метод очистки кеша
     *
     * @return
     */
    @Override
    public boolean clearCache() {
        return true;
    }

    /**
     * Получаем список всех доступных ролей, для заданных.
     * Т.е. мы получим все дочерние роли, включая те, что переданы в параметре
     *
     * @param authorities - заданная коллекция ролей
     * @return
     */
    @Override
    public Collection<GrantedAuthority> getReachableGrantedAuthorities(Collection<? extends GrantedAuthority> authorities) {
        System.out.println("----------------------" + authorities);
        final Set<GrantedAuthority> resultList = new HashSet<GrantedAuthority>();

        // если у пользователя нет ролей, то нет смысла выполнять нижеследующий код
        if (!CollectionUtils.isEmpty(authorities)) {
            // иерархия для каждой роли
            final Map<String, Set<GrantedAuthority>> hierarhyMap = new HashMap<String, Set<GrantedAuthority>>();

            // получаем все роли из БД
            final List<AppRole> appRoleList = getAllRoles();
            if (!CollectionUtils.isEmpty(appRoleList)) {
                for (AppRole appRole : appRoleList) {
                    // выстраиваем дерево зависимостей для каждой роли
                    createHierarhicalTree(appRole, appRole.getCode(), hierarhyMap);
                }
            }

            // теперь для каждой роли пользователя добавляем
            for (GrantedAuthority grantedAuthority : authorities) {
                final String key = grantedAuthority.getAuthority();
                if (hierarhyMap.containsKey(key)) {
                    // добавляем всех ее детей
                    resultList.addAll(hierarhyMap.get(key));
                }
            }
        }

        return resultList;
    }

    /**
     * Метод построения зависимостей для роли
     *
     * @param appRole
     * @param childCode
     * @param hierarhyMap
     */
    private void createHierarhicalTree(AppRole appRole, String childCode, Map<String, Set<GrantedAuthority>> hierarhyMap) {
        final String appRoleCode = appRole.getCode();

        // берем список детей из иерархии, если он там есть
        // если нет, создаем новый
        if (!hierarhyMap.containsKey(appRoleCode)) {
            final Set<GrantedAuthority> childrenSet = new HashSet<GrantedAuthority>();
            hierarhyMap.put(appRoleCode, childrenSet);
        }

        // добавляем ребенка
        final Set<GrantedAuthority> thisParent = hierarhyMap.get(appRoleCode);
        thisParent.add(new SimpleGrantedAuthority(childCode));

        // добавляем всех детей ребенка
        if (hierarhyMap.containsKey(childCode)) {
            thisParent.addAll(hierarhyMap.get(childCode));
        }

        // смотрим есть ли у роли родитель
        final AppRole parent = appRole.getParent();

        // если есть родитель, то идет рекурсия
        // если родителя нет, то это вершина дерева.
        // ничего не делаем
        if (parent != null) {
            createHierarhicalTree(parent, appRoleCode, hierarhyMap);
        }
    }

}
